package com.uw.uwrpc.core.consumer;

import com.uw.uwrpc.core.annotation.UWReference;
import com.uw.uwrpc.core.api.Filter;
import com.uw.uwrpc.core.api.LoadBalancer;
import com.uw.uwrpc.core.api.RegistryCenter;
import com.uw.uwrpc.core.api.Router;
import com.uw.uwrpc.core.api.RpcContext;
import com.uw.uwrpc.core.config.UWRpcProperty;
import com.uw.uwrpc.core.constant.ContextConstant;
import com.uw.uwrpc.core.exception.ErrorCode;
import com.uw.uwrpc.core.exception.RpcException;
import com.uw.uwrpc.core.meta.InstanceMeta;
import com.uw.uwrpc.core.meta.ServiceMeta;
import com.uw.uwrpc.core.util.FieldUtil;
import com.uw.uwrpc.core.util.SpringPackageScanUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 消费者启动器
 */
@Slf4j
@Setter
public class ConsumerBootstrap implements ApplicationContextAware, SmartInitializingSingleton {

    private ApplicationContext applicationContext;

    @Getter
    private UWRpcProperty uwRpcProperty;

    private RegistryCenter registryCenter;

    private final Map<String, Object> stub = new HashMap<>();


    @PostConstruct
    public void init() {
        registryCenter = applicationContext.getBean(RegistryCenter.class);
        uwRpcProperty = applicationContext.getBean(UWRpcProperty.class);
    }

    @Override
    public void afterSingletonsInstantiated() {
        log.info("ConsumerBootstrap starting");
        registryCenter.start();
        start();
        log.info("ConsumerBootstrap started");
    }


    @PreDestroy
    @SneakyThrows
    public void destroy() {
        registryCenter.stop();
    }

    public void start() {
        RpcContext rpcContext = createRpcContext();

        StopWatch stopWatch = new StopWatch();
        stopWatch.start("Bean 扫描，生成代理");
        // 获取扫描路径
        List<String> scanBasePathList = SpringPackageScanUtil.getScanBasePath(applicationContext);
        // 获取指定路径下的Bean。
        List<Object> beanList = SpringPackageScanUtil.scan(applicationContext, scanBasePathList);
        // 将 bean 中标记 UWReference 注解的字段，反射设置代理。
        try {
            for (Object bean : beanList) {
                List<Field> referenceFieldList = FieldUtil.getAnnotationFieldList(bean.getClass(), UWReference.class);
                for (Field referenceField : referenceFieldList) {
                    Class<?> serviceClass = referenceField.getType();
                    String serviceName = serviceClass.getCanonicalName();
                    Object service = stub.get(serviceName);
                    if (ObjectUtils.isEmpty(service)) {
                        // 生成代理类，具体客户端调用服务端由代理实现。
                        service = createConsumerProxy(serviceClass, rpcContext);
                        stub.put(serviceName, service);
                    }
                    referenceField.setAccessible(true);
                    referenceField.set(bean, service);
                }
            }
        } catch (IllegalAccessException e) {
            throw new RpcException(ErrorCode.undefined, e);
        } finally {
            stopWatch.stop();
            log.info(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
        }
    }

    private RpcContext createRpcContext() {
        LoadBalancer<InstanceMeta> loadBalancer = applicationContext.getBean(LoadBalancer.class);
        Router<InstanceMeta> router = applicationContext.getBean(Router.class);
        ConsumerInvoker consumerInvoker = applicationContext.getBean(ConsumerInvoker.class);
        List<Filter> filterList = applicationContext.getBeansOfType(Filter.class).values().stream().toList();

        Map<String, String> parameters = new HashMap<>();
        parameters.put(ContextConstant.retry_time, uwRpcProperty.getConsumer().getRetryTime().toString());
        parameters.put(ContextConstant.isolate_threshold, uwRpcProperty.getConsumer().getRetryTime().toString());
        parameters.put(ContextConstant.half_open_delay, uwRpcProperty.getConsumer().getHalfOpenDelay().toString());
        parameters.put(ContextConstant.half_open_initial_delay, uwRpcProperty.getConsumer().getHalfOpenInitialDelay().toString());
        parameters.put(ContextConstant.read_timeout, uwRpcProperty.getConsumer().getReadTimeout().toString());
        parameters.put(ContextConstant.write_timeout, uwRpcProperty.getConsumer().getWriteTimeout().toString());
        parameters.put(ContextConstant.connect_timeout, uwRpcProperty.getConsumer().getConnectTimeout().toString());

        return RpcContext.builder()
                .loadBalancer(loadBalancer)
                .router(router)
                .consumerInvoker(consumerInvoker)
                .filterList(filterList)
                .parameters(parameters)
                .build();
    }

    private Object createConsumerProxy(Class<?> serviceClass, RpcContext rpcContext) {
        ServiceMeta serviceMeta = ServiceMeta.builder()
                .appId(uwRpcProperty.getApp().getAppId())
                .namespace(uwRpcProperty.getApp().getNamespace())
                .env(uwRpcProperty.getApp().getEnv())
                .name(serviceClass.getCanonicalName())
                .build();

        InstanceHolder instanceHolder = new InstanceHolder();
        List<InstanceMeta> instanceMetaList = registryCenter.fetchAll(serviceMeta);
        instanceHolder.updateAllActiveInstance(instanceMetaList);

        registryCenter.subscribe(serviceMeta, (event) -> {
            instanceHolder.updateAllActiveInstance(event.getData());
        });

        return createConsumerProxy(serviceClass, rpcContext, instanceHolder);
    }

    private Object createConsumerProxy(Class<?> serviceClass, RpcContext rpcContext, InstanceHolder instanceHolder) {
        return Proxy.newProxyInstance(serviceClass.getClassLoader(),
                new Class[]{serviceClass},
                new UWInvocationHandler(serviceClass.getCanonicalName(), instanceHolder, rpcContext)
        );
    }

}
